passthrough: fix MSI-X table fixmap allocation
authorKeir Fraser <keir.fraser@citrix.com>
Tue, 17 Feb 2009 11:06:16 +0000 (11:06 +0000)
committerKeir Fraser <keir.fraser@citrix.com>
Tue, 17 Feb 2009 11:06:16 +0000 (11:06 +0000)
Currently, msix table pages are allocated a fixmap page per vector,
the available fixmap pages will be depleted when assigning devices
with large number of vectors.  This patch fixes it, and a bug that
prevents cross-page MSI-X table from working properly

It now allocates msix table fixmap pages per device, if the table
entries of two msix vectors share the same page, it will only be
mapped to fixmap once. A ref count is maintained so that it can
be unmapped when all the vectors are freed.

Also changes the meaning of msi_desc->mask_base from the va of msix
table start to the va of the target entry. The former one is currently
buggy (it always maps the first page but msix can support up to 2048
entries) and can't handle separately allocated pages.

Signed-off-by: Qing He <qing.he@intel.com>
xen/arch/x86/msi.c
xen/drivers/passthrough/pci.c
xen/include/asm-x86/fixmap.h
xen/include/asm-x86/msi.h
xen/include/xen/pci.h

index 44e617c5acce7325ddd08bd3b30be1611051c825..dc136f7f622923ae88276380c4e473cf141f9fb5 100644 (file)
 
 /* bitmap indicate which fixed map is free */
 DEFINE_SPINLOCK(msix_fixmap_lock);
-DECLARE_BITMAP(msix_fixmap_pages, MAX_MSIX_PAGES);
+DECLARE_BITMAP(msix_fixmap_pages, FIX_MSIX_MAX_PAGES);
 
 static int msix_fixmap_alloc(void)
 {
-    int i, rc = -1;
+    int i, rc = -ENOMEM;
 
     spin_lock(&msix_fixmap_lock);
-    for ( i = 0; i < MAX_MSIX_PAGES; i++ )
+    for ( i = 0; i < FIX_MSIX_MAX_PAGES; i++ )
         if ( !test_bit(i, &msix_fixmap_pages) )
             break;
-    if ( i == MAX_MSIX_PAGES )
+    if ( i == FIX_MSIX_MAX_PAGES )
         goto out;
     rc = FIX_MSIX_IO_RESERV_BASE + i;
     set_bit(i, &msix_fixmap_pages);
@@ -51,8 +51,66 @@ static int msix_fixmap_alloc(void)
 
 static void msix_fixmap_free(int idx)
 {
+    spin_lock(&msix_fixmap_lock);
     if ( idx >= FIX_MSIX_IO_RESERV_BASE )
         clear_bit(idx - FIX_MSIX_IO_RESERV_BASE, &msix_fixmap_pages);
+    spin_unlock(&msix_fixmap_lock);
+}
+
+static int msix_get_fixmap(struct pci_dev *dev, unsigned long table_paddr,
+                           unsigned long entry_paddr)
+{
+    int nr_page, idx;
+
+    nr_page = (entry_paddr >> PAGE_SHIFT) - (table_paddr >> PAGE_SHIFT);
+
+    if ( nr_page < 0 || nr_page >= MAX_MSIX_TABLE_PAGES )
+        return -EINVAL;
+
+    spin_lock(&dev->msix_table_lock);
+    if ( dev->msix_table_refcnt[nr_page]++ == 0 )
+    {
+        idx = msix_fixmap_alloc();
+        if ( idx < 0 )
+        {
+            dev->msix_table_refcnt[nr_page]--;
+            goto out;
+        }
+        set_fixmap_nocache(idx, entry_paddr);
+        dev->msix_table_idx[nr_page] = idx;
+    }
+    else
+        idx = dev->msix_table_idx[nr_page];
+
+ out:
+    spin_unlock(&dev->msix_table_lock);
+    return idx;
+}
+
+static void msix_put_fixmap(struct pci_dev *dev, int idx)
+{
+    int i;
+    unsigned long start;
+
+    spin_lock(&dev->msix_table_lock);
+    for ( i = 0; i < MAX_MSIX_TABLE_PAGES; i++ )
+    {
+        if ( dev->msix_table_idx[i] == idx )
+            break;
+    }
+    if ( i == MAX_MSIX_TABLE_PAGES )
+        goto out;
+
+    if ( --dev->msix_table_refcnt[i] == 0 )
+    {
+        start = fix_to_virt(idx);
+        destroy_xen_mappings(start, start + PAGE_SIZE);
+        msix_fixmap_free(idx);
+        dev->msix_table_idx[i] = 0;
+    }
+
+ out:
+    spin_unlock(&dev->msix_table_lock);
 }
 
 /*
@@ -122,8 +180,7 @@ static void read_msi_msg(struct msi_desc *entry, struct msi_msg *msg)
     case PCI_CAP_ID_MSIX:
     {
         void __iomem *base;
-        base = entry->mask_base +
-            entry->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE;
+        base = entry->mask_base;
 
         msg->address_lo = readl(base + PCI_MSIX_ENTRY_LOWER_ADDR_OFFSET);
         msg->address_hi = readl(base + PCI_MSIX_ENTRY_UPPER_ADDR_OFFSET);
@@ -199,8 +256,7 @@ static void write_msi_msg(struct msi_desc *entry, struct msi_msg *msg)
     case PCI_CAP_ID_MSIX:
     {
         void __iomem *base;
-        base = entry->mask_base +
-            entry->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE;
+        base = entry->mask_base;
 
         writel(msg->address_lo,
                base + PCI_MSIX_ENTRY_LOWER_ADDR_OFFSET);
@@ -288,8 +344,7 @@ static void msix_flush_writes(unsigned int vector)
         break;
     case PCI_CAP_ID_MSIX:
     {
-        int offset = entry->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE +
-            PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET;
+        int offset = PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET;
         readl(entry->mask_base + offset);
         break;
     }
@@ -330,8 +385,7 @@ static void msi_set_mask_bit(unsigned int vector, int flag)
         break;
     case PCI_CAP_ID_MSIX:
     {
-        int offset = entry->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE +
-            PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET;
+        int offset = PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET;
         writel(flag, entry->mask_base + offset);
         readl(entry->mask_base + offset);
         break;
@@ -392,13 +446,10 @@ int msi_free_vector(struct msi_desc *entry)
     {
         unsigned long start;
 
-        writel(1, entry->mask_base + entry->msi_attrib.entry_nr
-               * PCI_MSIX_ENTRY_SIZE
-               + PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET);
+        writel(1, entry->mask_base + PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET);
 
         start = (unsigned long)entry->mask_base & ~(PAGE_SIZE - 1);
-        msix_fixmap_free(virt_to_fix(start));
-        destroy_xen_mappings(start, start + PAGE_SIZE);
+        msix_put_fixmap(entry->dev, virt_to_fix(start));
     }
     list_del(&entry->list);
     xfree(entry);
@@ -500,8 +551,8 @@ static int msix_capability_init(struct pci_dev *dev,
     struct msi_desc *entry;
     int pos;
     u16 control;
-    unsigned long phys_addr;
-    u32 table_offset;
+    unsigned long table_paddr, entry_paddr;
+    u32 table_offset, entry_offset;
     u8 bir;
     void __iomem *base;
     int idx;
@@ -525,15 +576,17 @@ static int msix_capability_init(struct pci_dev *dev,
     table_offset = pci_conf_read32(bus, slot, func, msix_table_offset_reg(pos));
     bir = (u8)(table_offset & PCI_MSIX_FLAGS_BIRMASK);
     table_offset &= ~PCI_MSIX_FLAGS_BIRMASK;
-    phys_addr = msi->table_base + table_offset;
-    idx = msix_fixmap_alloc();
+    entry_offset = msi->entry_nr * PCI_MSIX_ENTRY_SIZE;
+
+    table_paddr = msi->table_base + table_offset;
+    entry_paddr = table_paddr + entry_offset;
+    idx = msix_get_fixmap(dev, table_paddr, entry_paddr);
     if ( idx < 0 )
     {
         xfree(entry);
-        return -ENOMEM;
+        return idx;
     }
-    set_fixmap_nocache(idx, phys_addr);
-    base = (void *)(fix_to_virt(idx) + (phys_addr & ((1UL << PAGE_SHIFT) - 1)));
+    base = (void *)(fix_to_virt(idx) + (entry_paddr & ((1UL << PAGE_SHIFT) - 1)));
 
     entry->msi_attrib.type = PCI_CAP_ID_MSIX;
     entry->msi_attrib.is_64 = 1;
@@ -548,9 +601,7 @@ static int msix_capability_init(struct pci_dev *dev,
     list_add_tail(&entry->list, &dev->msi_list);
 
     /* Mask interrupt here */
-    writel(1, entry->mask_base + entry->msi_attrib.entry_nr
-                * PCI_MSIX_ENTRY_SIZE
-                + PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET);
+    writel(1, entry->mask_base + PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET);
 
     *desc = entry;
     /* Restore MSI-X enabled bits */
@@ -675,9 +726,7 @@ static void __pci_disable_msix(struct msi_desc *entry)
 
     BUG_ON(list_empty(&dev->msi_list));
 
-    writel(1, entry->mask_base + entry->msi_attrib.entry_nr
-      * PCI_MSIX_ENTRY_SIZE
-      + PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET);
+    writel(1, entry->mask_base + PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET);
 
     pci_conf_write16(bus, slot, func, msix_control_reg(pos), control);
 }
index ec49b3d9772ea5d075f8bf5518739ac2713b3130..94dfb4e53d25da465da8454510157d1b33430065 100644 (file)
@@ -48,6 +48,7 @@ struct pci_dev *alloc_pdev(u8 bus, u8 devfn)
     pdev->domain = NULL;
     INIT_LIST_HEAD(&pdev->msi_list);
     list_add(&pdev->alldevs_list, &alldevs_list);
+    spin_lock_init(&pdev->msix_table_lock);
 
     return pdev;
 }
index 07afbc254dfcba3f37e24a086d79fbfbc3bbc213..1ef9a692028518b61d44d411af8e9e5cdef4c4d1 100644 (file)
@@ -50,7 +50,7 @@ enum fixed_addresses {
     FIX_IOMMU_MMIO_END = FIX_IOMMU_MMIO_BASE_0 + IOMMU_PAGES -1,
     FIX_TBOOT_SHARED_BASE,
     FIX_MSIX_IO_RESERV_BASE,
-    FIX_MSIX_IO_RESERV_END = FIX_MSIX_IO_RESERV_BASE + MAX_MSIX_PAGES -1,
+    FIX_MSIX_IO_RESERV_END = FIX_MSIX_IO_RESERV_BASE + FIX_MSIX_MAX_PAGES -1,
     __end_of_fixed_addresses
 };
 
index 1f18b7c5e3cc4f1283fef81f9c4589222c523462..a5f67e1655449f94e06aa48c0da11d16fbf4ff46 100644 (file)
@@ -49,9 +49,9 @@
 
 /* MAX fixed pages reserved for mapping MSIX tables. */
 #if defined(__x86_64__)
-#define MAX_MSIX_PAGES              512
+#define FIX_MSIX_MAX_PAGES              512
 #else
-#define MAX_MSIX_PAGES              32
+#define FIX_MSIX_MAX_PAGES              32
 #endif
 
 struct msi_info {
@@ -93,7 +93,7 @@ struct msi_desc {
 
        struct list_head list;
 
-       void __iomem *mask_base;
+       void __iomem *mask_base;        /* va for the entry in mask table */
        struct pci_dev *dev;
        int vector;
 
index 8ca77b31653d23e36cbac465abc1b975ce085d65..8ed5058e996238e3e79018767f2d21e5421b3499 100644 (file)
 #define PCI_BDF(b,d,f)  ((((b) & 0xff) << 8) | PCI_DEVFN(d,f))
 #define PCI_BDF2(b,df)  ((((b) & 0xff) << 8) | ((df) & 0xff))
 
+#define MAX_MSIX_TABLE_PAGES    8    /* 2048 entries */
 struct pci_dev {
     struct list_head alldevs_list;
     struct list_head domain_list;
+
     struct list_head msi_list;
+    int msix_table_refcnt[MAX_MSIX_TABLE_PAGES];
+    int msix_table_idx[MAX_MSIX_TABLE_PAGES];
+    spinlock_t msix_table_lock;
+
     struct domain *domain;
     const u8 bus;
     const u8 devfn;